# vim:ts=4:sw=4:noet:
""" Bytecode generator """
import spy
import logging
from c.base.support import CoreException

MAX_METHOD_SIZE = 120

class Bytecodes:
	PushInstance       = 0 
	PushTemporary      = 1
	PushLiteral        = 2
	PushConstant       = 3
	PushBlock          = 4
	DoPrimitive        = 5
	PopInstTemp        = 6
	AssignInstance     = 7
	AssignTemporary    = 8
	SendMessage        = 9
	SendMessage0       = 10
	SendMessage1       = 11
	SendMessage2       = 12
	SendUnary          = 13
	SendBinary         = 14
	DoSpecial          = 15

	# 0
	SpecialReturnSelf  = 1
	SpecialReturnStack = 2
	SpecialReturnBlock = 3
	SpecialDuplicate   = 4
	SpecialPop         = 5
	SpecialBranch      = 6
	SpecialBranchTrue  = 7
	SpecialBranchFalse = 8
	# 9
	# 10
	SpecialSendSuper   = 11
	SpecialPushSt      = 12
	SpecialPopSt       = 13
	# 14
	# 15

class CodeGen:
	def __init__(self, mem):
		self.mem = mem
		self.setup()

	def setup(self):
		self.rlabels = {}
		self.labeluse = {}
		self.literals = []
		self.bcs = []
		self.bclines = []
		self.firstLine = None
		# record if we've seen a super send, because we need to add
		# the super as a literal
		self.seenSuper = False
		# keep track of stack size - probably not entirely correct,
		# but at least overestimates, which shouldn't cause problems
		self.maxStack = 0
		self.curStack = 0

	def incStack(self, n=1):
		self.curStack += n
		self.maxStack = max(self.curStack, self.maxStack)

	def decStack(self, n=1):
		self.curStack -= n
		if self.curStack<0:
			logging.error("Possible stack underflow")
	
	def lineNum(self, l):
		if not self.firstLine:
			self.firstLine = l[0]
		nb = len(self.bcs)
		self.bclines.append([nb,(l[0]-self.firstLine)])
		
	def addBC(self, bc):
		self.bcs.append(bc)
		if len(self.bcs)>MAX_METHOD_SIZE:
			raise CoreException("Method too large")

	def addHighLow(self, hi, lo):
		self.addBC((hi<<4)|lo)

	def addSpecial(self, op):
		self.addBC((Bytecodes.DoSpecial<<4)|op)
		
	def fixLabels(self):
		for idx in self.labeluse:
			lb, ln = self.labeluse[idx]
			targ = self.rlabels[lb]
			if ln==1:
				self.bcs[idx] = targ
			else:
				raise CoreException("Unhandled jump target len: %d" % ln)
		
	def addLabelUse(self, lb, ln):
		self.labeluse[len(self.bcs)] = (lb,ln)

	def getLiteral(self, value):
		if value in self.literals:
			return self.literals.index(value)
		self.literals.append(value)
		if len(self.literals)>256:
			raise CoreException("Too many literals")
		return len(self.literals)-1
		
	def label(self, lb):
		self.rlabels[lb] = len(self.bcs) # mark the current bc count as a label
	
	def pushInstance(self, pos):
		self.addHighLow(Bytecodes.PushInstance, pos)
		self.incStack()

	def pushTemporary(self, pos):
		if pos==-1:
			pos = 0
		self.addHighLow(Bytecodes.PushTemporary, pos)
		self.incStack()
		
	def pushLiteral(self, name):
		if name == self.mem.nilObject:
			self.addHighLow(Bytecodes.PushConstant, 0xD)
		elif name == self.mem.trueObject:
			self.addHighLow(Bytecodes.PushConstant, 0xE)
		elif name == self.mem.falseObject:
			self.addHighLow(Bytecodes.PushConstant, 0xF)
		elif (isinstance(name, spy.SpySmallInt) and name.value==-1):
			self.addHighLow(Bytecodes.PushConstant, 0xB)
		elif (isinstance(name, spy.SpySmallInt) and name.value==-2):
			self.addHighLow(Bytecodes.PushConstant, 0xC)
		elif (isinstance(name, spy.SpySmallInt) and
					name.value<=10 and name.value>=0):
			self.addHighLow(Bytecodes.PushConstant, name.value)
		else:
			pos = self.getLiteral(name)
			if pos<15:
				self.addHighLow(Bytecodes.PushLiteral, pos)
			else:
				self.addHighLow(Bytecodes.PushLiteral, 0xF)
				self.addBC(pos)
		self.incStack()

	def popInstance(self, pos):
		if pos>=8:
			raise Exception("too many instance vars: %d" % pos)
		self.addHighLow(Bytecodes.PopInstTemp, pos)
	
	def popTemporary(self, pos):
		if pos>=8:
			raise Exception("too many temps: %d" % pos)
		self.addHighLow(Bytecodes.PopInstTemp, 8|pos)
		
	def storeInstance(self, pos):
		self.addHighLow(Bytecodes.AssignInstance, pos)
		
	def storeTemporary(self, pos):
		self.addHighLow(Bytecodes.AssignTemporary, pos)		
		
	def send(self, msg, nargs):
		if nargs==0:
			code = Bytecodes.SendMessage0
		elif nargs==1:
			code = Bytecodes.SendMessage1
		elif nargs==2:
			code = Bytecodes.SendMessage2
		else:
			code = Bytecodes.SendMessage
		pos = self.getLiteral(msg)
		if pos<15:
			self.addHighLow(code, pos)
		else:
			self.addHighLow(code, 0xF)
			self.addBC(pos)
		if nargs>=3:
			self.addBC(nargs+1)
		self.decStack(nargs)
		
	def sendu(self, msg, n):
		self.addHighLow(Bytecodes.SendUnary, n)
		
	def sendb(self, msg, n):
		self.addHighLow(Bytecodes.SendBinary, n)
		self.decStack()
				
	def pushBlock(self, args, end):
		self.addHighLow(Bytecodes.PushBlock, args)
		self.addLabelUse(end, 1)
		self.addBC(0)
		self.incStack()
	
	def prim(self, v, nargs):
		self.addHighLow(Bytecodes.DoPrimitive, nargs)
		self.addBC(v)
		self.decStack(nargs-1)

	def returnSelf(self):
		self.addSpecial(Bytecodes.SpecialReturnSelf)

	def returnStack(self):
		self.addSpecial(Bytecodes.SpecialReturnStack)
		self.decStack()
		
	def returnBlock(self):
		self.addSpecial(Bytecodes.SpecialReturnBlock)
		
	def cascadeStart(self):
		self.addSpecial(Bytecodes.SpecialDuplicate)
		self.incStack()

	def pop(self):
		self.addSpecial(Bytecodes.SpecialPop)
		self.decStack()
		
	def branch(self, lb):
		self.addSpecial(Bytecodes.SpecialBranch)
		self.addLabelUse(lb,1)
		self.addBC(0)
		
	def branchIfTrue(self, lb):
		self.addSpecial(Bytecodes.SpecialBranchTrue)
		self.addLabelUse(lb,1)
		self.addBC(0)
		self.decStack()
		
	def branchIfFalse(self, lb):
		self.addSpecial(Bytecodes.SpecialBranchFalse)
		self.addLabelUse(lb,1)
		self.addBC(0)
		self.decStack()
	
	def pushSmalltalk(self):
		self.addSpecial(Bytecodes.SpecialPushSt)
		self.incStack()
		
	def popSmalltalk(self):
		self.addSpecial(Bytecodes.SpecialPopSt)
		
	def sendSuper(self, msg, nargs):
		self.addSpecial(Bytecodes.SpecialSendSuper)
		self.addBC(self.getLiteral(msg))
		self.addBC(nargs+1)
		self.seenSuper = True
	
	def cascadeEnd(self):
		self.pop()
